package org.listbeast.core.datasource.impl;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.log4j.Logger;
import org.listbeast.core.common.ProcessingException;
import org.listbeast.core.dataobjects.SongCollection;
import org.listbeast.core.dataobjects.SongCollectionImpl;
import org.listbeast.core.dataobjects.YoutubeSong;
import org.listbeast.core.datasource.SongReader;
import org.listbeast.core.filters.SongFilter;
import org.listbeast.core.youtubeclient.YoutubeClient;

import com.google.gdata.data.youtube.VideoEntry;
import com.google.gdata.util.ServiceException;

public class YoutubeSongReader implements SongReader {

	private static final int RELEVANT_VIDEOS_STARTING_OFFSET = 1;

	//info: offset cannot be more than 1000 due to youtube api restrictions
	private static final int MAX_MOST_VIEWED_VIDEOS_TO_SEARCH_FOR = 500;

	private static final int RELEVANT_VIDEOS_NUM = 50;

	private static final Logger LOG = Logger.getLogger(YoutubeSongReader.class);

	private final int maxResults;
	private final Collection<String> artistNames;
	private final YoutubeClient youtubeClient = new YoutubeClient();
	private final SongFilter songFilter;

	public YoutubeSongReader(int maxResults, Collection<String> artistNames,
			SongFilter songFilter) {
		this.artistNames = artistNames;
		this.maxResults = maxResults;
		this.songFilter = songFilter;
	}

	/**
	 * return a collection of youtube songs for a given number of artists
	 */
	public SongCollection<YoutubeSong> getSongs() {

		if ((artistNames == null) || (artistNames.isEmpty())) {
			throw new ProcessingException("No artists were found!");
		}

		SongCollection<YoutubeSong> youtubeSongCollection = new SongCollectionImpl<YoutubeSong>();

		for (String artist : artistNames) {
			SongCollection<YoutubeSong> youtubeSongsPerArtist = getVideoTitles(artist, maxResults);

			for (YoutubeSong youtubeSong : youtubeSongsPerArtist.getAllSongs()) {
				youtubeSongCollection.addSong(youtubeSong);
			}
		}

		return youtubeSongCollection;
	}

	/**
	 * Method returns a set of youtube songs for a given artist. The purpose is
	 * to return the most popular songs of this artist. To do this I first get
	 * the 50 most relevant videos displayed by youtube for this artist. Then, I
	 * get pages of the most viewed videos and if a video belongs to the most
	 * relevant videos list, I add this to my set. I do it like this because
	 * many of the most viewed videos are usually not relevant to my keyword and
	 * I want a mechanism to ignore these. Method quits after searching going
	 * through 500 most viewed videos or after maxResults videos have been
	 * found.
	 * 
	 * @param artist
	 *            Name of artist to perform search for in youtube
	 * @param maxResults
	 *            Maximum number of returned results for artist
	 * @return A set of youtube songs
	 */
	private SongCollection<YoutubeSong> getVideoTitles(String artist, int maxResults) {

		LOG.info("-----------------------------------------");
		LOG.info("Running Search for '" + artist + "'");

		SongCollection<YoutubeSong> foundSongs = new SongCollectionImpl<YoutubeSong>();

		try {

			List<VideoEntry> relevantVideoEntries = youtubeClient.findVideosOrderedByRelevance(artist, RELEVANT_VIDEOS_STARTING_OFFSET, RELEVANT_VIDEOS_NUM);
			SongCollection<YoutubeSong> relevantToKeywordYoutubeSongs = mapVideoEntriesToYoutubeSongs(artist, relevantVideoEntries);

			int starting_offset = 1;
			int page_size = 20;
			do {
				LOG.debug("artistName: " + artist + ", offset: "
						+ starting_offset
						+ " ,step (video results per search): " + page_size
						+ " found titles: " + foundSongs.getAllSongs().size());

				// get maxResults videos with most view counts
				List<VideoEntry> mostViewedVideoEntries = youtubeClient.findVideosOrderedByViewCount(artist, starting_offset, page_size);

				// iterate through videos with most views and select the ones
				// that belong to the relevant videos list only
				for (VideoEntry videoEntry : mostViewedVideoEntries) {

					YoutubeSong mostViewedSong = mapVideoEntryToYoutubeSong(artist, videoEntry);

					if (foundSongs.getAllSongs().size() == maxResults) {
						//we have found maxResults number of youtube songs for this artist. Need to quit method and return these.
						return foundSongs;
					} else if (relevantToKeywordYoutubeSongs.contains(mostViewedSong)) {
						//check if a similar song title has NOT already been found. We do not want multiple youtube entries of same song (live, not live, etc)
						if (!songFilter.findMatch(foundSongs, mostViewedSong)) {

							LOG.info("YoutubeSongReader: getVideoTitles(): Returned youtube song= "
									+ mostViewedSong);

							foundSongs.addSong(mostViewedSong);
						}
					}

				}
				starting_offset += page_size;

			} while ((foundSongs.getAllSongs().size() < maxResults)
					&& (starting_offset < MAX_MOST_VIEWED_VIDEOS_TO_SEARCH_FOR));

		} catch (IOException e) {
			LOG.error(e);
		} catch (ServiceException e) {
			LOG.error(e);
		}

		return foundSongs;
	}

	private YoutubeSong mapVideoEntryToYoutubeSong(String artist, VideoEntry videoEntry) {

		YoutubeSong youtubeSong = null;

		if (videoEntry.getTitle() != null) {
			youtubeSong = new YoutubeSong(artist, videoEntry.getTitle().getPlainText());
		}
		return youtubeSong;
	}
	
	private SongCollection<YoutubeSong> mapVideoEntriesToYoutubeSongs(String artist, List<VideoEntry> videoEntries) {
		SongCollection<YoutubeSong> youtubeSongs = new SongCollectionImpl<YoutubeSong>();
		for (VideoEntry ve : videoEntries) {
			if (ve.getTitle() != null) {
				youtubeSongs.addSong(new YoutubeSong(artist, ve.getTitle().getPlainText()));
			}
		}
		return youtubeSongs;
	}

}
